package tech

import (
	"ssp/set"
)

func (g *Grid) clearupWings(cand int, pivot *Cell, wings []*Cell) (used bool, rmCells []*Cell) {
	for i, wing := range wings {
		if wing.cands.Has(cand) {
			wings[0], wings[i] = wings[i], wings[0]
			break
		}
	}
	relCells := g.seenCells(wings[0])
	for _, wing := range wings[1:] {
		if !wing.cands.Has(cand) {
			continue
		}
		relCells = shareCells(relCells, g.seenCells(wing))
	}

	if pivot.cands.Has(cand) {
		relCells = shareCells(relCells, g.seenCells(pivot))
	}

	for _, cell := range relCells {
		if cell.Equal(pivot) {
			continue
		}
		if cell.canRemoveCands(set.NewSet(cand)) {
			used = true
			rmCells = append(rmCells, cell)
		}
	}
	return
}

func (s *Solve) XYWing(g *Grid) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, pivot := range row.cells {
			if pivot.cands.Count() != 2 {
				continue
			}
			if ok, result := g.wingCell(pivot, 2, "XY-Wing"); ok {
				if OUTPUTRESULT {
					results = append(results, &result)
				}
				used = true
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}

func (s *Solve) XYZWing(g *Grid) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, pivot := range row.cells {
			if pivot.cands.Count() != 3 {
				continue
			}
			if ok, result := g.wingCell(pivot, 2, "XYZ-Wing"); ok {
				if OUTPUTRESULT {
					results = append(results, &result)
				}
				used = true
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}

func (s *Solve) WXYZWing(g *Grid) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, pivot := range row.cells {
			if pivot.cands.Count() > 4 {
				continue
			}
			if ok, result := g.wingCell(pivot, 3, "WXYZ-Wing"); ok {
				if OUTPUTRESULT {
					results = append(results, &result)
				}
				used = true
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}

func (s *Solve) VWXYZWing(g *Grid) (used bool, results []*Result) {
	for _, row := range g.rows {
		for _, pivot := range row.cells {
			if pivot.solved() {
				continue
			}
			if pivot.cands.Count() > 5 {
				continue
			}
			if ok, result := g.wingCell(pivot, 4, "VWXYZ-Wing"); ok {
				if OUTPUTRESULT {
					results = append(results, &result)
				}
				used = true
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}

func (g *Grid) wingCell(pivot *Cell, wingCount int, sname string) (used bool, result Result) {
	for _, wings := range g.getWings(pivot, wingCount) {
		ok, rmCand := verifyWings(pivot, wings)
		if !ok {
			continue
		}
		//fmt.Println(ok, rmCand)
		if ok, rmCells := g.clearupWings(rmCand, pivot, wings); ok {
			if OUTPUTRESULT {
				result.addRemoved(set.NewSet(rmCand), rmCells)
				g1 := result.getHelpCandsGroup()
				g1.add(set.NewSet(rmCand), append(wings, pivot))
				g2 := result.getHelpCandsGroup()
				g2.add(pivot.cands.Diff(set.NewSet(rmCand)), append(wings, pivot))
				result.addHelpCells([]*Cell{pivot})
				result.addHelpCells(wings)
				result.setStrategyName(sname)
			}
			if CHANGE {
				removeCands(set.NewSet(rmCand), rmCells)
			}
			used = true
			return
		}
	}
	return
}

func (g *Grid) getWings(pivot *Cell, wingCount int) (allWings [][]*Cell) {
	seenCells := g.seenCells(pivot)
	cells := make([]*Cell, 0)
	for _, seenCell := range seenCells {
		count := seenCell.cands.Count()
		if count >= 2 && count <= wingCount {
			cells = append(cells, seenCell)
		}
	}
	return combine(cells, wingCount)
}

func verifyWings(pivot *Cell, wings []*Cell) (used bool, cand int) {
	if isSameHouse(wings) {
		return
	}

	wingCount := len(wings)
	allCands := unionCellCands(wings)
	if allCands.Count() != wingCount+1 {
		return
	}

	wingGroups := divideWingGroups(wings)
	z := unionCellCands(wingGroups[0])
	for _, group := range wingGroups[1:] {
		cands := unionCellCands(group)
		if cands.Count() != len(group)+1 {
			return
		}
		z = z.Intersect(cands)
	}
	if z.Count() != 1 {
		return
	}

	//if allCands != pivot.cands && allCands.Diff(z) != pivot.cands {
	if !allCands.IsSuperset(pivot.cands) {
		return
	}
	if pivot.cands.IsSuperset(z) {
		if pivot.cands.Count() < 3 {
			return
		}
	} else {
		if pivot.cands.Count() < 2 {
			return
		}
	}

	used = true
	cand = z.GetList()[0]
	return
}

func divideWingGroups(wings []*Cell) (wingGroups [][]*Cell) {
	wingCount := len(wings)
	switch wingCount {
	case 2:
		wingGroups = append(wingGroups, []*Cell{wings[0]})
		wingGroups = append(wingGroups, []*Cell{wings[1]})
		return
	case 3:
		for _, group := range combine(wings, 2) {
			if !isSameHouse(group) {
				continue
			}
			wingGroups = append(wingGroups, group)
			for _, last := range wings {
				if !last.isMemberOf(group) {
					wingGroups = append(wingGroups, []*Cell{last})
					break
				}
			}
			return
		}
		for _, wing := range wings {
			wingGroups = append(wingGroups, []*Cell{wing})
		}
		return
	case 4:
		wingGroups = divideWingGroupsForVWXYZ(wings)
		return
	}
	return
}

func divideWingGroupsForVWXYZ(wings []*Cell) (wingGroups [][]*Cell) {
	for _, group := range combine(wings, 3) {
		if !isSameHouse(group) {
			continue
		}
		wingGroups = append(wingGroups, group)
		for _, last := range wings {
			if !last.isMemberOf(group) {
				wingGroups = append(wingGroups, []*Cell{last})
				return
			}
		}
	}
	for _, group := range combine(wings, 2) {
		if !isSameHouse(group) {
			continue
		}
		wingGroups = append(wingGroups, group)

		lastGroup := []*Cell{}
		for _, last := range wings {
			if !last.isMemberOf(group) {
				lastGroup = append(lastGroup, last)
			}
		}

		if isSameHouse(lastGroup) {
			wingGroups = append(wingGroups, lastGroup)
			return
		} else {
			wingGroups = append(wingGroups, []*Cell{lastGroup[0]})
			wingGroups = append(wingGroups, []*Cell{lastGroup[1]})
			return
		}
	}

	for _, wing := range wings {
		wingGroups = append(wingGroups, []*Cell{wing})
	}
	return
}

func (s *Solve) WWing(g *Grid) (used bool, results []*Result) {
	return g.useTechInAllHouse(g.wWingHouse)
}

func (g *Grid) wWingHouse(house *House) (used bool, results []*Result) {
	for cand := 0; cand < 9; cand++ {
		if ok, rs := g.wWingCand(house, cand); ok {
			results = append(results, rs...)
			used = true
		}
	}
	return
}

func (g *Grid) wWingCand(house *House, cand int) (used bool, results []*Result) {
	pivots := chooseHasCandCells(house, cand)
	if len(pivots) != 2 {
		return
	}
	if house.kind != 2 && pivots[0].boxNum == pivots[1].boxNum {
		// to be do in boxes
		return
	}

	wings1 := filterWWingEnds(cand, g.seenCells(pivots[0]))
	wings2 := filterWWingEnds(cand, g.seenCells(pivots[1]))

	for _, wing1 := range wings1 {
		if wing1 == pivots[1] {
			continue
		}
		for _, wing2 := range wings2 {
			if wing2 == pivots[0] {
				continue
			}
			if wing1 == wing2 || isSameHouse([]*Cell{wing1, wing2}) {
				continue
			}
			if wing1.cands != wing2.cands {
				continue
			}
			removedCand := wing1.cands.GetList()[0]
			if removedCand == cand {
				removedCand = wing1.cands.GetList()[1]
			}
			if ok, rmCells := g.clearupWWing(
				removedCand, pivots, []*Cell{wing1, wing2}); ok {
				used = true

				if OUTPUTRESULT {
					result := new(Result)
					result.addRemoved(set.NewSet(removedCand), rmCells)
					g1 := result.getHelpCandsGroup()
					g1.add(set.NewSet(cand), append(pivots, wing1, wing2))
					g2 := result.getHelpCandsGroup()
					g2.add(set.NewSet(removedCand), []*Cell{wing1, wing2})
					result.addHelpCells(pivots)
					result.addHelpCells([]*Cell{wing1, wing2})
					result.setStrategyName("W-Wing")
					results = append(results, result)

				}
				if CHANGE {
					removeCands(set.NewSet(removedCand), rmCells)
				}
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}

	return
}

func filterWWingEnds(cand int, cells []*Cell) (endCells []*Cell) {
	for _, cell := range cells {
		if cell.cands.Count() != 2 || !cell.cands.Has(cand) {
			continue
		}
		endCells = append(endCells, cell)
	}
	return
}

func (g *Grid) clearupWWing(cand int, cells []*Cell, wings []*Cell) (
	used bool, rmCells []*Cell) {
	relCells := g.seenCells(wings[0])
	for _, cell := range wings[1:] {
		relCells = shareCells(relCells, g.seenCells(cell))
	}

	for _, cell := range relCells {
		if cell.isMemberOf(cells) {
			continue
		}
		if cell.canRemoveCand(cand) {
			rmCells = append(rmCells, cell)
			used = true
		}
	}
	return
}
